// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/child_process_launcher_helper.h"

#include <memory>

#include "base/i18n/icu_util.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "content/browser/android/child_process_launcher_android.h"
#include "content/browser/child_process_launcher_helper_posix.h"
#include "content/browser/file_descriptor_info_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_descriptors.h"
#include "content/public/common/content_switches.h"
#include "gin/v8_initializer.h"

namespace content {
namespace internal {

    namespace {

        // Callback invoked from Java once the process has been started.
        void ChildProcessStartedCallback(
            ChildProcessLauncherHelper* helper,
            base::ProcessHandle handle,
            int launch_result)
        {
            // TODO(jcivelli): Remove this by defining better what happens on what thread
            // in the corresponding Java code.
            ChildProcessLauncherHelper::Process process;
            process.process = base::Process(handle);
            if (BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER)) {
                helper->PostLaunchOnLauncherThread(
                    std::move(process),
                    launch_result,
                    false); // post_launch_on_client_thread_called
                return;
            }

            bool on_client_thread = BrowserThread::CurrentlyOn(
                static_cast<BrowserThread::ID>(helper->client_thread_id()));
            BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
                base::Bind(&ChildProcessLauncherHelper::PostLaunchOnLauncherThread,
                    helper,
                    base::Passed(std::move(process)),
                    launch_result,
                    on_client_thread));
            if (on_client_thread) {
                ChildProcessLauncherHelper::Process process;
                process.process = base::Process(handle);
                helper->PostLaunchOnClientThread(std::move(process), launch_result);
            }
        }

    } // namespace

    void ChildProcessLauncherHelper::BeforeLaunchOnClientThread()
    {
        // Android only supports renderer, sandboxed utility and gpu.
        std::string process_type = command_line()->GetSwitchValueASCII(switches::kProcessType);
        CHECK(process_type == switches::kGpuProcess || process_type == switches::kRendererProcess ||
#if BUILDFLAG(ENABLE_PLUGINS)
            process_type == switches::kPpapiPluginProcess ||
#endif
            process_type == switches::kUtilityProcess)
            << "Unsupported process type: " << process_type;

        // Non-sandboxed utility or renderer process are currently not supported.
        DCHECK(process_type == switches::kGpuProcess || !command_line()->HasSwitch(switches::kNoSandbox));
    }

    mojo::edk::ScopedPlatformHandle
    ChildProcessLauncherHelper::PrepareMojoPipeHandlesOnClientThread()
    {
        return mojo::edk::ScopedPlatformHandle();
    }

    std::unique_ptr<FileDescriptorInfo>
    ChildProcessLauncherHelper::GetFilesToMap()
    {
        DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);

        // Android WebView runs in single process, ensure that we never get here when
        // running in single process mode.
        CHECK(!command_line()->HasSwitch(switches::kSingleProcess));

        std::unique_ptr<FileDescriptorInfo> files_to_register = CreateDefaultPosixFilesToMap(*command_line(), child_process_id(),
            mojo_client_handle());

#if defined(V8_USE_EXTERNAL_STARTUP_DATA)
        base::MemoryMappedFile::Region region;
        auto maybe_register = [&region, &files_to_register](int key, int fd) {
            if (fd != -1)
                files_to_register->ShareWithRegion(key, fd, region);
        };
        maybe_register(
            kV8NativesDataDescriptor,
            gin::V8Initializer::GetOpenNativesFileForChildProcesses(&region));
        maybe_register(
            kV8SnapshotDataDescriptor32,
            gin::V8Initializer::GetOpenSnapshotFileForChildProcesses(&region, true));
        maybe_register(
            kV8SnapshotDataDescriptor64,
            gin::V8Initializer::GetOpenSnapshotFileForChildProcesses(&region, false));

        command_line()->AppendSwitch(::switches::kV8NativesPassedByFD);
        command_line()->AppendSwitch(::switches::kV8SnapshotPassedByFD);
#endif // defined(V8_USE_EXTERNAL_STARTUP_DATA)

#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
        int fd = base::i18n::GetIcuDataFileHandle(&region);
        files_to_register->ShareWithRegion(kAndroidICUDataDescriptor, fd, region);
#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE

        return files_to_register;
    }

    void ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
        const FileDescriptorInfo& files_to_register,
        base::LaunchOptions* options)
    {
    }

    ChildProcessLauncherHelper::Process
    ChildProcessLauncherHelper::LaunchProcessOnLauncherThread(
        const base::LaunchOptions& options,
        std::unique_ptr<FileDescriptorInfo> files_to_register,
        bool* is_synchronous_launch,
        int* launch_result)
    {
        *is_synchronous_launch = false;

        StartChildProcess(command_line()->argv(),
            child_process_id(),
            files_to_register.get(),
            base::Bind(&ChildProcessStartedCallback,
                RetainedRef(this)));

        return Process();
    }

    void ChildProcessLauncherHelper::AfterLaunchOnLauncherThread(
        const ChildProcessLauncherHelper::Process& process,
        const base::LaunchOptions& options)
    {
    }

    // static
    base::TerminationStatus ChildProcessLauncherHelper::GetTerminationStatus(
        const ChildProcessLauncherHelper::Process& process,
        bool known_dead,
        int* exit_code)
    {
        if (IsChildProcessOomProtected(process.process.Handle()))
            return base::TERMINATION_STATUS_OOM_PROTECTED;
        return base::GetTerminationStatus(process.process.Handle(), exit_code);
    }

    // static
    bool ChildProcessLauncherHelper::TerminateProcess(
        const base::Process& process, int exit_code, bool wait)
    {
        StopChildProcess(process.Handle());
        return true;
    }

    // static
    void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
        ChildProcessLauncherHelper::Process process)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
        VLOG(1) << "ChromeProcess: Stopping process with handle "
                << process.process.Handle();
        StopChildProcess(process.process.Handle());
    }

    // static
    void ChildProcessLauncherHelper::SetProcessBackgroundedOnLauncherThread(
        base::Process process, bool background)
    {
        SetChildProcessInForeground(process.Handle(), !background);
    }

} // namespace internal
} // namespace content
